package lucene.server.database.pool;
import lombok.extern.slf4j.Slf4j;
import lucene.server.common.utils.ThreadUtil;
import lucene.server.database.utils.DialectUtil;

import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class PoolManager {
    private static boolean isInit = false;
    private static final ReentrantLock lock = new ReentrantLock();
    //数据库连接池
    //todo list需要用Vector改造,保证线程安全
    private static final ConcurrentHashMap<String, List<PooledConnection>> connectPools = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<String, Map<String,String>> connectProps = new ConcurrentHashMap<>();
    public static void poolInit() {
        if(!isInit){
            new Thread(() -> { validateConnection(); }, "validateConnection").start();
            shutdownHook();
            isInit = true;
        }
    }

    //服务器配置修改了
    public static void modifyConnection(String name,String url, String username, String password, String driverClass,String dbType) throws SQLException {
        //如果数据池不存在
        if (!connectPools.containsKey(name)) {
            createConnection(name, url, username, password, driverClass,dbType);
        }
        List<PooledConnection> list = connectPools.get(name);
        for (PooledConnection pool : list) {
            pool.modifyConnection(name, url, username, password, driverClass,dbType);
        }
    }

    //服务器配置修改了
    public static void removeConnection(String connectionName) {
        //如果数据池不存在
        if (!connectPools.containsKey(connectionName)) {
            return;
        }
        List<PooledConnection> list = connectPools.get(connectionName);
        for (PooledConnection pool : list) {
            pool.shutdown();
        }
        connectPools.remove(connectionName);
    }

    public static boolean contains(String connectionName){
        return connectPools.containsKey(connectionName);
    }
    public static PooledConnection getConnection(String connectionName) {
        if (!connectPools.containsKey(connectionName)) {
            throw new RuntimeException("缺少数据库配置信息.");
        }
        try {
            lock.lock();
            //获取可用连接对象
            PooledConnection poolConnection = getAvailableConnection(connectionName);
            if (poolConnection == null){
                createConnection(connectionName);
            }
            //没有可用连接对象时，等待连接对象的释放或者创建新的连接对象使用
            //每次等待30毫秒,超过配置值跳出
            int waitTime = 0;
            while (poolConnection == null && waitTime < PoolConfig.getPoolWaitTimeout()) {
                TimeUnit.MILLISECONDS.sleep(30);
                waitTime = waitTime + 30;
                poolConnection = getAvailableConnection(connectionName);
            }
            return poolConnection;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            lock.unlock();
        }
    }

    public static String getStatus() {
        StringBuilder statusStr = new StringBuilder();
        Iterator<String> iterator1 = connectPools.keySet().iterator();
        while (iterator1.hasNext()) {
            String key = iterator1.next();
            List<PooledConnection> list = connectPools.get(key);
            int allConn = list.size();
            int busyConn = 0;
            for (PooledConnection conn : list) {
                if (conn.isBusy()) {
                    busyConn++;
                }
            }
            statusStr.append(String.format("data:%s , allConn:% d, busyConn:%d ", key, allConn, busyConn));
        }
        return statusStr.toString();
    }

    //获取可用连接对象
    private static synchronized PooledConnection getAvailableConnection(String connectionName) {
        //如果有配置,没有池化处理,先池化处理直接返回数据连接
        if (!connectPools.containsKey(connectionName)) {
            return null;
        }
        List<PooledConnection> list = connectPools.get(connectionName);
        for (PooledConnection pool : list) {
            if (!pool.isBusy()) {
                return pool.use();
            }
        }
        return null;
    }
    public static void  createConnection(Map<String, String> connectionInfo) throws SQLException {
        String name = connectionInfo.get("connName");
        String url = connectionInfo.get("connUrl");
        String username = connectionInfo.get("connUser");
        String password = connectionInfo.get("connPassword");
        String driverClass = connectionInfo.get("connDriver");
        String dbType = connectionInfo.get("connType");
        createConnection(name,url, username, password, driverClass,dbType);
    }

    //创建数据库连接
    public static void createConnection(Properties properties) {
        String databaseType = properties.getProperty("type").toLowerCase();
        String name = "cdc_"+properties.getProperty("name");
        String url = DialectUtil.getUrl(databaseType, properties);
        String username = properties.getProperty("username");
        String password = properties.getProperty("password");
        String driverClass = DialectUtil.getDriver(databaseType);
        storeConnectProps(name,url, username, password, driverClass,databaseType);

        //如果不存在数据库池
        List<PooledConnection> list;
        if (!connectPools.containsKey(name)) {
            list = Collections.synchronizedList(new ArrayList<>());
            connectPools.put(name, list);
        } else {
            list = connectPools.get(name);
        }
        //如果超过最大连接数
        if (list.size() >= PoolConfig.getPoolMaxSize()) {
            return;
        }
        PooledConnection pooledConnection = new PooledConnection(name,url, username, password, driverClass,databaseType);
        list.add(pooledConnection);
        log.info("createConnection:" + name + " size:" + list.size());
    }

    //创建数据库连接
    public static void createConnection(String name,String url, String username, String password, String driverClass,String dbType) throws SQLException {
        storeConnectProps(name,url, username, password, driverClass,dbType);
        //如果不存在数据库池
        List<PooledConnection> list;
        if (!connectPools.containsKey(name)) {
            list = Collections.synchronizedList(new ArrayList<>());
            connectPools.put(name, list);
        } else {
            list = connectPools.get(name);
        }
        //如果超过最大连接数
        if (list.size() >= PoolConfig.getPoolMaxSize()) {
            return;
        }
        PooledConnection pooledConnection = new PooledConnection(name,url, username, password, driverClass,dbType);
        list.add(pooledConnection);
    }
    //创建数据库连接
    public static void createConnection(String connectionName) throws SQLException {

        if (!connectProps.containsKey(connectionName)){
            return;
        }
        Map<String,String> connProps =  connectProps.get(connectionName);
        createConnection(connectionName,connProps.get("url"),connProps.get("username"),connProps.get("password"),connProps.get("driverClass"),connProps.get("dbType"));
    }

    private static void shutdownHook() {
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                log.info("开始关闭数据库连接池");
                try {
                    for (List<PooledConnection> connectionList : connectPools.values()) {
                        int i = 0;
                        for (PooledConnection connection : connectionList) {
                            i++;
                            connection.shutdown();
                        }
                    }
                } catch (Exception e) {
                } finally {
                    log.info("关闭数据库连接池结束");
                }
            }
        });
    }

    public static void validateConnection() {
        while (true) {
            ThreadUtil.sleep(PoolConfig.getValidateTimeMillis());
            connectPools.values().forEach(connectPools->{
                int cutSize = connectPools.size()-PoolConfig.getPoolInitSize();

                Iterator<PooledConnection> iterator = connectPools.iterator();
                while (iterator.hasNext()) {
                    PooledConnection connection = iterator.next();
                    if (cutSize>0 && !connection.isBusy()){
                        connection.shutdown();
                        iterator.remove();
                        cutSize--;
                    }else if (!connection.isBusy()){
                        connection.validateConnection();
                    }
                }
            });
        }
    }

    public static List<Map<String,Object>> getPoolInfo() {
        List<Map<String,Object>> poolInfo = new ArrayList<>();
        Iterator<String> iterator1 = connectPools.keySet().iterator();
        while (iterator1.hasNext()) {
            Map<String,Object> server = new LinkedHashMap<>();
            String key = iterator1.next();
            List<Object>  connections = new ArrayList<>();
            for (PooledConnection connection : connectPools.get(key)) {
                try{
                    if(!server.containsKey("server")){
                        server.put("serverInfo",  connection.getName());
                        server.put("productInfo",  connection.getProductInfo());
                    }
                    connections.add(connection.getConnectionInfo());
                }catch (Exception e){
                    server.put("serverInfo", connection.getName());
                    server.put("productInfo", "");
                }
            }
            server.put("connections",connections);
            poolInfo.add(server);
        }
        return poolInfo;
    }
    private static void storeConnectProps(String name,String url, String username, String password, String driverClass,String dbType){
        Map<String,String> connProps = connectProps.get(name);
        if(connProps == null){
            connProps = new HashMap<>();
            connectProps.put(name,connProps);
        }
        connProps.put("name",name);
        connProps.put("url",url);
        connProps.put("username",username);
        connProps.put("password",password);
        connProps.put("driverClass",driverClass);
        connProps.put("dbType",dbType);
    }
}

